package cc.shacocloud.kotlin.tools.http

import cc.shacocloud.kotlin.tools.Slf4j
import cc.shacocloud.kotlin.tools.http.Http.APPLICATION_JSON
import cc.shacocloud.kotlin.tools.http.Http.APPLICATION_XML
import cc.shacocloud.kotlin.tools.http.Http.FORM_URLENCODED
import cc.shacocloud.kotlin.tools.http.Http.TEXT_PLAIN
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.json.Json
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.internal.EMPTY_REQUEST
import okhttp3.logging.HttpLoggingInterceptor
import java.io.ByteArrayInputStream
import java.io.File
import java.io.IOException
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine


/**
 * @author 思追(shaco)
 */
@Slf4j
class HttpClient(
    private val url: String,
    private val method: String,
    private var httpClient: OkHttpClient = HttpClientBuilder.DEFAULT
) {

    private val header: MutableMap<String, String> = mutableMapOf(
        "accept" to "application/json;charset=UTF-8",
        "connection" to "keep-alive",
    )

    private val interceptors = mutableListOf<Interceptor>()

    private var requestBody: RequestBody? = null

    // 是否忽略tls握手
    private var ignoreTLSHandshake = false

    // 是否使用自定义证书握手
    private var certificate: String? = null

    // 日志级别，默认为 HEADERS
    private var level = HttpLoggingInterceptor.Level.HEADERS

    /**
     * 设置日志级别
     */
    fun logLevel(level: HttpLoggingInterceptor.Level): HttpClient {
        this.level = level
        return this
    }

    /**
     * 设置连接超时 单位 秒
     */
    fun connectTimeout(timeout: Int): HttpClient {
        this.httpClient = httpClient.newBuilder().connectTimeout(timeout.toLong(), TimeUnit.SECONDS).build()
        return this
    }

    /**
     * 设置写入超时 单位 秒
     */
    fun writeTimeout(timeout: Int): HttpClient {
        this.httpClient = httpClient.newBuilder().writeTimeout(timeout.toLong(), TimeUnit.SECONDS).build()
        return this
    }

    /**
     * 设置读取超时 单位 秒
     */
    fun readTimeout(timeout: Int): HttpClient {
        this.httpClient = httpClient.newBuilder().readTimeout(timeout.toLong(), TimeUnit.SECONDS).build()
        return this
    }

    /**
     * 添加请求头
     */
    fun header(name: String, value: String): HttpClient {
        header[name.trim()] = value.trim()
        return this
    }

    fun headers(headers: Map<String, String>): HttpClient {
        headers.entries.forEach {
            header(it.key, it.value)
        }
        return this
    }

    /**
     * 纯文本格式请求
     */
    fun textPlain(body: String): HttpClient {
        this.requestBody = body.toRequestBody(TEXT_PLAIN)
        return this
    }

    /**
     * text/plain
     * xml字符请求体
     */
    fun applicationXml(body: String): HttpClient {
        this.requestBody = body.toRequestBody(APPLICATION_XML)
        return this
    }

    /**
     * json字符请求体
     */
    fun applicationJson(body: String): HttpClient {
        this.requestBody = body.toRequestBody(APPLICATION_JSON)
        return this
    }

    /**
     * json对象请求体
     *
     * 基于 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 实现的 json 序列化
     */
    fun <T> applicationJson(serializer: SerializationStrategy<T>, value: T): HttpClient {
        this.requestBody = Json.encodeToString(serializer, value).toRequestBody(APPLICATION_JSON)
        return this
    }

    /**
     * json对象请求体
     *
     * 基于 [jackson](https://github.com/FasterXML/jackson) 实现的 json 序列化
     */
    fun applicationJson(value: Any): HttpClient {
        this.requestBody = when (value) {
            is CharSequence -> value.toString().toRequestBody(APPLICATION_JSON)
            else -> cc.shacocloud.kotlin.tools.json.Json.encode(value).toRequestBody(APPLICATION_JSON)
        }
        return this
    }

    /**
     * 多部份表单请求
     */
    fun formParams(params: Map<String, Any>): HttpClient {
        val builder = MultipartBody.Builder()
        builder.setType(MultipartBody.FORM)
        params.forEach { (name: String, value: Any) ->
            if (value is CharSequence) {
                builder.addFormDataPart(name, value.toString())
            } else if (value is File) {
                val contentType = Files.probeContentType(value.toPath()) ?: "application/octet-stream"
                val body = value.asRequestBody(
                    contentType.toMediaTypeOrNull() ?: "application/octet-stream".toMediaType()
                )
                builder.addFormDataPart(name, value.name, body)
            } else {
                throw IllegalArgumentException("参数键 $name 为不支持的参数类型：${value}，仅支持字符串和文件")
            }
        }
        this.requestBody = builder.build()
        return this
    }

    /**
     * 表单参数请求
     */
    fun formUrlencoded(params: Map<String, String>): HttpClient {
        val body = params.entries.joinToString("&") { it.key + "=" + URLEncoder.encode(it.value, "UTF-8") }
        this.requestBody = body.toRequestBody(FORM_URLENCODED)
        return this
    }

    /**
     * 自定义http请求客户端
     */
    fun customizeClient(httpClient: OkHttpClient): HttpClient {
        this.httpClient = httpClient
        return this
    }


    /**
     * 是否忽略 tls 握手结果，默认为 false 不忽略
     *
     * @param ignore true为忽略
     */
    fun ignoreTLSHandshake(ignore: Boolean): HttpClient {
        this.ignoreTLSHandshake = ignore
        return this
    }

    /**
     * 自定义 tls 握手证书
     */
    fun customizeCertificate(certificate: String?): HttpClient {
        this.certificate = certificate
        return this
    }

    /**
     * 添加拦截器
     */
    fun interceptor(interceptor: Interceptor): HttpClient {
        this.interceptors += interceptor
        return this
    }

    /**
     * 添加拦截器
     */
    fun interceptors(interceptors: List<Interceptor>): HttpClient {
        this.interceptors.addAll(interceptors)
        return this
    }


    /**
     * 发起请求
     */
    fun send(): Response {
        val response = build().execute()
        return response
    }

    /**
     * 发起异步请求
     */
    suspend fun asyncSend(): Response {
        val call = build()

        return suspendCoroutine { continuation ->
            call.enqueue(object : Callback {

                override fun onResponse(call: Call, response: Response) {
                    continuation.resume(response)
                }

                override fun onFailure(call: Call, e: IOException) {
                    continuation.resumeWithException(e)
                }
            })
        }
    }

    private fun build(): Call {
        val builder = Request.Builder()

        // 请求地址
        builder.url(url)

        // 请求头
        header.forEach { (name: String, value: String) -> builder.header(name, value) }

        when (method) {
            "GET" -> builder.get()
            "HEAD" -> builder.head()
            "POST" -> builder.post(requestBody ?: EMPTY_REQUEST)
            "PUT" -> builder.put(requestBody ?: EMPTY_REQUEST)
            "PATCH" -> builder.patch(requestBody ?: EMPTY_REQUEST)
            "DELETE" -> builder.delete(requestBody)
            else -> builder.method(method, requestBody)
        }
        // 自定义证书握手
        if (!certificate.isNullOrBlank()) {
            httpClient = ByteArrayInputStream(certificate!!.toByteArray(StandardCharsets.UTF_8)).use {
                CustomizeTLSCertificateHandshakeUtil.builder(httpClient, it)
            }
        } else if (ignoreTLSHandshake) {
            httpClient = IgnoreTLSHandshakeUtil.builder(httpClient)
        }

        // 日志拦截器
        httpClient = httpClient.newBuilder().apply {
            for (interceptor in interceptors) {
                addInterceptor(interceptor)
            }

            addInterceptor(HttpRequestLoggingInterceptor(level))
        }.build()


        return httpClient.newCall(builder.build())
    }
}
